package com.codeark.appengine;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;
import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.BaseDatastoreService;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.mail.MailService;
import com.google.appengine.api.mail.MailServiceFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFactory;
import com.google.appengine.api.quota.QuotaService;
import com.google.appengine.api.quota.QuotaServiceFactory;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.xmpp.XMPPService;
import com.google.appengine.api.xmpp.XMPPServiceFactory;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
import com.google.inject.servlet.RequestScoped;

/**
 * <p>
 * An AppEngine module that sets the stage common GAE services injection
 * (instead of using the GAE static factories). uses Fluent Interface like
 * coding style to build:
 * </p>
 * 
 * <pre>
 * Guice.createInjector(AppEngineModule.build().withDatastoreService().withUrlFetchService().withMemcacheService());
 * </pre>
 * 
 * <h2>Services injection:</h2>
 * 
 * <pre>
 * class Classy {
 * 	&#064;Inject
 * 	public Classy(Memcache service, DatastoreService data) {
 * 		...
 * 	}
 * }
 * </pre>
 * 
 * <i>note: You can inject BaseDatastoreService instead of DatastoreService or
 * AsyncDatastoreService</i>
 *
 * 
 * @author Yaniv Kessler (yaniv at codeark dot com)
 * 
 */
public class AppEngineModule extends AbstractModule {

	private static final Logger log = Logger.getLogger(AppEngineModule.class.getName());

	private LocalServiceTestHelper helper;

	private MailService mailService;

	private DatastoreService datastoreService;
	private AsyncDatastoreService asyncDatastoreService;

	private UserService userService;
	private OAuthService oauthService;
	private URLFetchService urlFetchService;
	private XMPPService xmppService;
	private QuotaService quotaService;
	private MemcacheService memcacheService;
	private ChannelService channelService;

	private AppEngineModule() {
	}

	@Override
	protected void configure() {
		if (helper != null)
			bind(LocalServiceTestHelper.class).toInstance(helper);

		if (memcacheService != null)
			bind(MemcacheService.class).toInstance(memcacheService);

		if (quotaService != null)
			bind(QuotaService.class).toInstance(quotaService);

		if (xmppService != null)
			bind(XMPPService.class).toInstance(xmppService);

		if (userService != null)
			bind(UserService.class).toInstance(userService);

		if (oauthService != null)
			bind(OAuthService.class).toInstance(oauthService);

		if (datastoreService != null) {
			bind(BaseDatastoreService.class).toInstance(datastoreService);
			bind(DatastoreService.class).toInstance(datastoreService);
		}

		if (asyncDatastoreService != null) {
			bind(BaseDatastoreService.class).toInstance(asyncDatastoreService);
			bind(AsyncDatastoreService.class).toInstance(asyncDatastoreService);
		}

		if (mailService != null)
			bind(MailService.class).toInstance(mailService);

		if (urlFetchService != null) {
			bind(URLFetchService.class).toInstance(urlFetchService);
			bind(new TypeLiteral<UrlFetchServiceFacade<String>>() {
			}).toProvider(StringUrlFetchServiceFacadeProvider.class);
			bind(new TypeLiteral<UrlFetchServiceFacade<Object>>() {
			}).annotatedWith(JsonContent.class).toProvider(JsonUrlFetchServiceFacadeProvider.class);
		}

		if (channelService != null) {
			bind(ChannelService.class).toInstance(channelService);
		}

		log.info(String.format("%s initialized", AppEngineModule.class.getSimpleName()));
	}

	static class StringUrlFetchServiceFacadeProvider implements Provider<UrlFetchServiceFacade<String>> {
		private final URLFetchService service;

		@Inject
		public StringUrlFetchServiceFacadeProvider(URLFetchService service) {
			this.service = service;
		}

		@Override
		public UrlFetchServiceFacade<String> get() {
			return new StringUrlFetchServiceFacade(service);
		}
	}

	static class JsonUrlFetchServiceFacadeProvider implements Provider<UrlFetchServiceFacade<Object>> {
		private final URLFetchService service;

		@Inject
		public JsonUrlFetchServiceFacadeProvider(URLFetchService service) {
			this.service = service;
		}

		@Override
		public UrlFetchServiceFacade<Object> get() {
			return new JsonUrlFetchServiceFacade(service);
		}
	}

	@BindingAnnotation
	@Retention(RetentionPolicy.RUNTIME)
	@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
	public @interface JsonContent {
	}

	public boolean isJunitTestEnvironment() {
		return helper != null;
	}

	public AppEngineModule withMemcacheService() {
		this.memcacheService = MemcacheServiceFactory.getMemcacheService();
		return this;
	}

	/**
	 * BaseDatastoreService binds will be done according to last invocation of
	 * this method or withAsyncDatastoreService
	 * 
	 * @return
	 */
	public AppEngineModule withDatastoreService() {
		this.datastoreService = DatastoreServiceFactory.getDatastoreService();
		this.asyncDatastoreService = null;
		return this;
	}

	/**
	 * BaseDatastoreService binds will be done according to last invocation of
	 * this method or withDatastoreService
	 * 
	 * @return
	 */
	public AppEngineModule withAsyncDatastoreService() {
		this.datastoreService = null;
		this.asyncDatastoreService = DatastoreServiceFactory.getAsyncDatastoreService();
		return this;
	}

	public AppEngineModule withQuotaService() {
		this.quotaService = QuotaServiceFactory.getQuotaService();
		return this;
	}

	public AppEngineModule withUrlFetchService() {
		this.urlFetchService = URLFetchServiceFactory.getURLFetchService();
		return this;
	}

	public AppEngineModule withUserService() {
		this.userService = UserServiceFactory.getUserService();
		return this;
	}

	public AppEngineModule withXMPPService() {
		this.xmppService = XMPPServiceFactory.getXMPPService();
		return this;
	}

	public AppEngineModule withOAuthService() {
		this.oauthService = OAuthServiceFactory.getOAuthService();
		return this;
	}

	public AppEngineModule withMailService() {
		this.mailService = MailServiceFactory.getMailService();
		return this;
	}

	public AppEngineModule withChannelService() {
		this.channelService = ChannelServiceFactory.getChannelService();
		return this;
	}

	public static final AppEngineModule build() {
		return new AppEngineModule();
	}

	public static final AppEngineModule buildWithLocalTestEnvironment() {
		AppEngineModule aem = new AppEngineModule();
		aem.helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
		aem.helper.setUp();
		return aem;
	}

	
}